interface EventList {
    type: "until" | "once";
    callback: () => any;
}

export default class EventEmitter {
    private eventObj: { [name: string]: EventList[] };

    constructor() {
        this.eventObj = {};
    }

    public on(eventName: string, callback: (...args: any[]) => any) {
        if (!this.eventObj[eventName]) {
            this.eventObj[eventName] = [];
        }
        this.eventObj[eventName].push({
            callback,
            type: "until",
        });
    }

    public once(eventName: string, callback: (...args: any[]) => any) {
        if (!this.eventObj[eventName]) {
            this.eventObj[eventName] = [];
        }
        this.eventObj[eventName].push({
            callback,
            type: "once",
        });
    }

    public emit(eventName: string, ...args: any) {
        if (!this.eventObj[eventName]) {
            return false;
        }
        for (const { callback, type } of this.eventObj[eventName]) {
            callback.apply(undefined, args);
        }
        this.eventObj[eventName] = this.eventObj[eventName].filter((item) => item.type !== "once");
        if (!this.eventObj[eventName].length) {
            delete this.eventObj[eventName];
        }
        return true;
    }

    public removeListener(eventName: string, callback: (...args: any[]) => any) {
        if (!this.eventObj[eventName]) {
            return false;
        }
        for (let i = 0; i < this.eventObj[eventName].length; i++) {
            const item = this.eventObj[eventName][i];
            if (item.callback === callback) {
                this.eventObj[eventName].splice(i, 1);
                if (this.eventObj[eventName].length) {
                    delete this.eventObj[eventName];
                }
                return true;
            }
        }
        return false;
    }

    public exists(eventName: string): boolean {
        return this.eventObj[eventName] && this.eventObj[eventName].length > 0;
    }
}